// Copyright (c) 2019 Presto Labs Pte. Ltd.
// Author: xguo

#pragma once

#include <memory>
#include <sstream>
#include <string>
#include <vector>

#include "coin2/exchange/base/symbology/derivative_util.h"
#include "coin2/exchange/base/symbology/product.h"

namespace coin2::exchange::base::symbology {

template <typename CurrencyT, typename ProductT>
class OptionsProduct : public CurrencyPairProduct<CurrencyT> {
 public:
  OptionsProduct(
      const CurrencyT& base,
      const CurrencyT& quote,
      const std::string& native_product,
      const std::string& native_subscription_symbol,
      const ExchangeType exchange,
      const ContractType contract_type,
      const int64_t expiry_timestamp,
      const OptionsSide options_side,
      const int64_t strike_price,
      const int64_t resolve_timestamp,
      const int64_t strike_price_multiplier)
      : CurrencyPairProduct<CurrencyT>(
            base,
            quote,
            ConvertToOptionsNormStr(base, quote, expiry_timestamp,
                                    options_side, strike_price, strike_price_multiplier),
            native_product,
            ConvertToOptionsSubscriptionSymbol(base, quote, contract_type,
                                               options_side, strike_price,
                                               strike_price_multiplier),
            native_subscription_symbol,
            MarketType::Options,
            exchange),
        contract_type_(contract_type),
        expiry_timestamp_(expiry_timestamp),
        resolve_timestamp_(resolve_timestamp),
        expiry_str_(ConvertToExpiryStr(expiry_timestamp_)),
        options_side_(options_side),
        strike_price_(strike_price),
        strike_price_multiplier_(strike_price_multiplier) {}

 public:
  int64_t expiry() const { return expiry_timestamp_; }

  const std::string& expiry_str() const { return expiry_str_; }

  ContractType contract_type() const { return contract_type_; }

  const std::string& contract_type_name() const { return ContractTypeToString(contract_type_); }

  OptionsSide options_side() const { return options_side_; }

  const std::string& options_side_name() const { return OptionsSideToString(options_side_); }

  double strike_price() const { return strike_price_; }

  int64_t strike_price_multiplier() const { return strike_price_multiplier_; }

  int64_t resolve_timestamp() const { return resolve_timestamp_; }

  static std::unique_ptr<ProductT> FromNormStrImpl(
      const std::string& product_str,
      const int64_t resolve_timestamp) {
    std::string base_str;
    std::string quote_str;
    std::string expiry_str;
    std::string side_str;
    std::string strike_price_str;
    std::string strike_price_multiplier_str;
    bool success;
    success = ParseOptionsNormStr(
        product_str,
        &base_str,
        &quote_str,
        &expiry_str,
        &side_str,
        &strike_price_str,
        &strike_price_multiplier_str);

    if (success) {
      auto base = CurrencyT::FromNormStrNoThrow(base_str, resolve_timestamp);
      auto quote = CurrencyT::FromNormStrNoThrow(quote_str, resolve_timestamp);

      if (base && quote) {
        std::string native_product;
        std::string native_subscription_symbol;
        ContractType contract_type;
        int64_t expiry_timestamp;
        OptionsSide options_side;
        int64_t strike_price;
        int64_t strike_price_multiplier;

        success = ProductT::CalculateParams(
            *base,
            *quote,
            expiry_str,
            side_str,
            strike_price_str,
            strike_price_multiplier_str,
            resolve_timestamp,
            &native_product,
            &native_subscription_symbol,
            &contract_type,
            &expiry_timestamp,
            &options_side,
            &strike_price,
            &strike_price_multiplier);

        if (success) {
          return std::make_unique<ProductT>(
              *base,
              *quote,
              native_product,
              native_subscription_symbol,
              contract_type,
              expiry_timestamp,
              options_side,
              strike_price,
              resolve_timestamp,
              strike_price_multiplier);
        }
      }
    }
    return {};
  }

  IProduct* Clone() const final {
    return new ProductT(
        CurrencyPairProduct<CurrencyT>::base_impl(),
        CurrencyPairProduct<CurrencyT>::quote_impl(),
        CurrencyPairProduct<CurrencyT>::native_product(),
        CurrencyPairProduct<CurrencyT>::feed_native_symbol(),
        contract_type(),
        expiry_timestamp_,
        options_side_,
        strike_price_,
        resolve_timestamp_,
        strike_price_multiplier_);
  }

 private:
  static bool ParseOptionsNormStr(
      const std::string& options_norm_str,
      std::string* base,
      std::string* quote,
      std::string* expiry_str,
      std::string* side_str,
      std::string* strike_price_str,
      std::string* strike_price_multiplier_str) {
    std::vector<std::string> out;
    out = Split(options_norm_str, "-.");
    if (out.size() < 5) {
      return false;
    }
    *base = out.at(0);
    *quote = out.at(1);
    *expiry_str = out.at(2);
    *side_str = out.at(3);
    *strike_price_str = out.at(4);
    *strike_price_multiplier_str = "1";
    if (out.size() >= 6) {
      *strike_price_multiplier_str = out.at(5);
    }
    return true;
  }

  static std::string ConvertToOptionsNormStr(
      const ICurrency& base,
      const ICurrency& quote,
      const int64_t expiry_timestamp,
      OptionsSide options_side,
      int64_t strike_price,
      int64_t strike_price_multiplier) {
    std::stringstream ss;
    ss << base.currency() << "-" << quote.currency() << "."
       << ConvertToExpiryStr(expiry_timestamp) << "."
       << OptionsSideToString(options_side) << "."
       << strike_price << "."
       << strike_price_multiplier;
    return ss.str();
  }

  static std::string ConvertToOptionsSubscriptionSymbol(
      const ICurrency& base,
      const ICurrency& quote,
      const ContractType contract_type,
      OptionsSide options_side,
      int64_t strike_price,
      int64_t strike_price_multiplier) {
    std::stringstream ss;
    ss << base.currency() << "-" << quote.currency() << "."
       << ContractTypeToString(contract_type) << "."
       << OptionsSideToString(options_side) << "."
       << strike_price;
    if (strike_price_multiplier != 1) {
      ss << "." << strike_price_multiplier;
    }
    return ss.str();
  }

  static std::string ConvertToExpiryStr(int64_t expiry_timestamp) {
    if (expiry_timestamp == PERPETUAL_TIMESTAMP) {
      return "PERPETUAL";
    } else if (expiry_timestamp == IMMEDIATE_TIMESTAMP) {
      return "IMMEDIATE";
    } else {
      return ToDateStr(expiry_timestamp);
    }
  }

 public:
  static const int64_t PERPETUAL_TIMESTAMP = 4102416000LL * 1000000000LL;
  static const int64_t IMMEDIATE_TIMESTAMP = 0;

 private:
  const ContractType contract_type_;
  const int64_t expiry_timestamp_;
  const int64_t resolve_timestamp_;
  const std::string expiry_str_;
  const OptionsSide options_side_;
  const int64_t strike_price_;
  const int64_t strike_price_multiplier_;  // To convert strike price to integer, for now always 1.
};

}  // namespace coin2::exchange::base::symbology
